use std::rc::Rc;
use crate::expr::{Expression, Variable};
use crate::scanner::Token;


pub trait StmtVisitor<R> {
    fn visit_print_stmt(&mut self, expr: &StmtPrint) -> R;
    fn visit_expr_stmt(&mut self, expr: &StmtExpr) -> R;
    fn visit_var_stmt(&mut self, stmt: &StmtVar) -> R;
    fn visit_block_stmt(&mut self, stmt: &StmtBlock) -> R;
    fn visit_if_stmt(&mut self, stmt: &StmtIf) -> R;
    fn visit_while_stmt(&mut self, stmt: &StmtWhile) -> R;
    fn visit_break_stmt(&self) -> R;
    fn visit_continue_stmt(&self) -> R;
    fn visit_class_stmt(&mut self, stmt: &StmtClass) -> R;
    fn visit_function_stmt(&mut self, stmt: &StmtFunction) -> R;
    fn visit_return_stmt(&mut self, stmt: &StmtReturn) -> R;
}

pub trait StmtTrait<R> {
    fn accept(&self, visitor: &mut dyn StmtVisitor<R>) -> R;
}

#[derive(Debug, Clone)]
pub struct StmtExpr {
    pub expr: Expression
}

#[derive(Debug, Clone)]
pub struct StmtPrint {
    pub expr: Expression
}

#[derive(Debug, Clone)]
pub struct StmtVar {
    pub name: Token,
    pub initializer: Expression
}

#[derive(Debug, Clone)]
pub struct StmtBlock {
    pub stmts: Vec<Stmt>
}

#[derive(Debug, Clone)]
pub struct StmtIf {
    pub condition: Expression,
    pub then_stmt: Rc<Stmt>,
    pub else_stmt: Option<Rc<Stmt>>
}

#[derive(Debug, Clone)]
pub struct StmtWhile {
    pub condition: Expression,
    pub body: Rc<Stmt>
}

#[derive(Debug, Clone)]
pub struct StmtClass {
    pub name: Token,
    pub superclass: Option<Variable>,
    pub methods: Vec<StmtFunction>
}

#[derive(Debug, Clone)]
pub struct StmtFunction {
    pub name: Token,
    pub params: Vec<Token>,
    pub body: Vec<Stmt>
}

#[derive(Debug, Clone)]
pub struct StmtReturn {
    pub keyword: Token,
    pub value: Option<Expression>
}

#[derive(Debug, Clone)]
pub enum Stmt {
    Expression(StmtExpr),
    Print(StmtPrint),
    Var(StmtVar),
    Block(StmtBlock),
    If(StmtIf),
    While(StmtWhile),
    Class(StmtClass),
    Function(StmtFunction),
    Return(StmtReturn),
    Break,
    Continue
}

impl<R> StmtTrait<R> for Stmt {
    fn accept(&self, visitor: &mut dyn StmtVisitor<R>) -> R {
        match self {
            Stmt::Expression(stmt) => visitor.visit_expr_stmt(stmt),
            Stmt::Print(stmt) => visitor.visit_print_stmt(stmt),
            Stmt::Var(stmt) => visitor.visit_var_stmt(stmt),
            Stmt::Block(stmt) => visitor.visit_block_stmt(stmt),
            Stmt::If(stmt) => visitor.visit_if_stmt(stmt),
            Stmt::While(stmt) => visitor.visit_while_stmt(stmt),
            Stmt::Break => visitor.visit_break_stmt(),
            Stmt::Continue => visitor.visit_continue_stmt(),
            Stmt::Function(stmt) => visitor.visit_function_stmt(stmt),
            Stmt::Return(stmt) => visitor.visit_return_stmt(stmt),
            Stmt::Class(stmt) => visitor.visit_class_stmt(stmt)
        }
    }
}